﻿using System;
using System.Collections.Generic;
using System.Linq;
using Coder.Workflow.Searcher;
using Coder.Workflow.Stores;
using Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;

namespace Coder.Workflow
{
    public abstract class WorkflowManager<T, TProcessInstance>
        where T : WorkflowDbContext
        where TProcessInstance : ProcessInstance
    {
        protected readonly WorkflowDbContext Context;
        protected readonly IWorkActivityStore<T> WorkActivityStore;

        protected WorkflowManager(T context, IWorkActivityStore<T> workActivityStore)
        {
            Context = context ?? throw new ArgumentNullException(nameof(context));
            WorkActivityStore = workActivityStore;
        }

        protected virtual IQueryable<WorkActivity> FillQuery(IQueryable<WorkActivity> workActivities)
        {
            return workActivities;
        }

        public virtual TProcessInstance GetById(int id)
        {
            return Context
                .Set<TProcessInstance>().Include(f => f.WorkProcess).ThenInclude(f => f.Items)
                .Include(f => f.User)

                .FirstOrDefault(f => f.Id == id);
        }

        public virtual WorkActivity GetWorkActivity(int id, bool includeExtentInfo = false)

        {
            IQueryable<WorkActivity> item = Context.WorkActivities

                    .Include(f => f.DepositUser)
                    .Include(f => f.ProcessInstance)
                    .ThenInclude(f => f.WorkProcess)
                    .ThenInclude(f => f.Items)
                    .ThenInclude(f => f.Role)
                    .Include(f => f.ProcessInstance)
                    .ThenInclude(f => f.User)
                    .Include(f => f.ProcessInstance)
                ;

            if (includeExtentInfo) item = FillQuery(item);



            var act = item.FirstOrDefault(f => f.Id == id);
            if (act.WorkTask == null)
                Context.Entry(act).Reference(f => f.WorkTask).Load();
            return act;
        }


        protected virtual void Resolve(WorkActivity activity, string comment,
            out IEnumerable<WorkActivity> nextWorkActivities)
        {
            nextWorkActivities = null;
            activity.Resolve(comment);
            if (!MoveOn(activity, out nextWorkActivities))
            {
                if (activity.ProcessInstance == null)
                    Context.Entry(activity).Reference(act => act.ProcessInstance).Load();
                activity.ProcessInstance.Complete();
                OnProcessComplete(activity.ProcessInstance);
            }

            Context.Update(activity);

            activity.WorkTask.OnComplete(activity, Context);
        }


        public WorkActivity Resolve(int id, string comment, out IEnumerable<WorkActivity> nextWorkActivities)
        {
            var trans = Context.Database.BeginTransaction();
            try
            {
                var activity = GetWorkActivity(id, true);
                Resolve(activity, comment, out nextWorkActivities);
                Context.SaveChanges();
                trans.Commit();


                return activity;
            }
            catch
            {
                trans.Rollback();
                throw;
            }
            finally
            {
                trans.Dispose();
            }
        }

        public virtual WorkActivity Reject(int id, string comment, out IEnumerable<WorkActivity> nextWorkActivities)
        {
            var hasTrans = Context.Database.CurrentTransaction != null;
            var tran = hasTrans ? null : Context.Database.BeginTransaction();
            try
            {
                var activity = GetWorkActivity(id, true);

                if (!activity.WorkTask.CanReject) throw new WorkflowException(activity.WorkTask.Name + "不允许退回");
                activity.Reject(comment);
                MoveOn(activity, out nextWorkActivities);
                activity.WorkTask.OnComplete(activity, Context);
                Context.SaveChanges();
                tran?.Commit();
                return activity;
            }
            catch
            {
                tran?.Rollback();
                throw;
            }
            finally
            {
                tran?.Dispose();
            }
        }


        public virtual WorkActivity Assign(int id, string userName)
        {
            if (userName == null) throw new ArgumentNullException(nameof(userName));
            var user = Context.GetByUserName(userName);
            var item = Context.WorkActivities.FirstOrDefault(f => f.Id == id);
            if (item == null)
                return null;

            item.AssignTo(user);

            Context.SaveChanges();
            return item;
        }

        public virtual WorkActivity GiveUp(int id, string userName = null)
        {
            var activity = Context.WorkActivities.Include(f => f.WorkTask).FirstOrDefault(f => f.Id == id);
            if (activity == null)
                return null;
            if (!activity.WorkTask.CanGiveUp) throw new WorkflowException(activity.WorkTask.Name + "不允许放弃处理。");

            if (!string.IsNullOrEmpty(userName))
            {
                var user = Context.GetByUserName(userName);
                activity.Giveup(user);
            }
            else
            {
                activity.Giveup();
            }

            Context.Update(activity);
            Context.SaveChanges();
            return activity;
        }

        protected virtual IEnumerable<WorkActivity> StartProcess(TProcessInstance order)
        {
            Context.Update(order);
            order.Start(out var workActivities);
            foreach (var activity in workActivities)
                Context.Update(activity);
            Context.SaveChanges();
            return workActivities;
        }

        public virtual IEnumerable<WorkActivity> Start(TProcessInstance order)
        {
            if (order.Status != ProcessInstanceStatus.Created) throw new WorkflowException("只有在草拟状态下的工作实例，才能启动.");
            var hasTrans = Context.Database.CurrentTransaction != null;
            var trans = !hasTrans ? Context.Database.BeginTransaction() : null;
            try
            {
                var result = StartProcess(order);
                trans?.Commit();
                return result;
            }
            catch
            {
                trans?.Rollback();
                throw;
            }
            finally
            {
                trans?.Dispose();
            }
        }

        private IEnumerable<WorkActivity> GetCurrentWorkActivities(WorkActivity activity)
        {
            var previousActivityId = Context.WorkActivities.Where(act => act.WorkTask.Id == (activity.WorkTask.Id - 1))
                .OrderByDescending(f => f.Id).Select(f => f.Id).Take(1).FirstOrDefault();




            var activites = Context.WorkActivities
 .Where(p => p.WorkTask.Id == activity.WorkTask.Id &&
             p.ProcessInstance.Id == activity.ProcessInstance.Id
             && p.Id > previousActivityId
                            );

            return activites;
        }

        protected virtual void OnProcessComplete(ProcessInstance instance)
        {
        }

        private bool MoveOn(WorkActivity activity, out IEnumerable<WorkActivity> nextWorkActivities)
        {
            nextWorkActivities = null;
            var activities = GetCurrentWorkActivities(activity);
            var resolve = true;
            var allDone = true;
            foreach (var act in activities)
                switch (act.Status)
                {
                    case WorkActivityStatus.Reject:
                        resolve = false;
                        break;
                    case WorkActivityStatus.Processing:
                    case WorkActivityStatus.UnAssign:
                        allDone = false;
                        break;
                }

            if (allDone)
                if (resolve)
                    return GoToNext(activity, out nextWorkActivities);
                else
                    return GoPrevious(activity, out nextWorkActivities);
            return false;
        }

        /// <summary>
        ///     返回true，证明还有否则结束
        /// </summary>
        /// <param name="current"></param>
        /// <param name="nextWorkActivities"></param>
        /// <returns></returns>
        protected virtual bool GoToNext(WorkActivity current, out IEnumerable<WorkActivity> nextWorkActivities)
        {
            nextWorkActivities = null;
            if (current.ProcessInstance.WorkProcess.TryGetNext(current.WorkTask.Id, out var nextWork))
                foreach (var nextWorkTask in nextWork)
                    if (nextWorkTask.PreCondition(current.ProcessInstance))
                    {
                        nextWorkActivities = nextWorkTask.Create(current.ProcessInstance);
                        if (nextWorkActivities == null || nextWorkActivities.Any() == false)
                            throw new WorkflowException("nexWorkTask creae workActivities fail");
                        AssignByResolve(nextWorkActivities, current);
                        foreach (var next in nextWorkActivities) Context.Update(next);
                        return true;
                    }
                    else
                    {
                        return false;
                    }

            return false;
        }

        /// <summary>
        ///     能够返回上一步
        /// </summary>
        /// <param name="activity"></param>
        /// <param name="nextWorkActivities"></param>
        /// <returns></returns>
        private bool GoPrevious(WorkActivity activity, out IEnumerable<WorkActivity> nextWorkActivities)
        {
            var getPrevious = activity.ProcessInstance.WorkProcess
                .TryGetPrevious(activity.WorkTask, out var previousTask);

            if (getPrevious)
            {
                nextWorkActivities = previousTask.Create(activity.ProcessInstance);
                if (nextWorkActivities != null)
                {
                    foreach (var workActivity in nextWorkActivities)
                    {
                        AssignByReject(workActivity, activity);
                        Context.Update(workActivity);
                    }

                    Context.SaveChanges();
                    return true;
                }
            }

            throw new WorkflowException("没有上一步可以再'回退'的工作。");
        }

        /// <summary>
        ///     为解决的下一个环节分配用户
        /// </summary>
        /// <param name="assignTo"></param>
        /// <param name="resolveWorkActivity"></param>
        protected virtual void AssignByResolve(IEnumerable<WorkActivity> assignTo, WorkActivity resolveWorkActivity)
        {
        }

        /// <summary>
        ///     为拒绝的下一个环节分配用户
        /// </summary>
        /// <param name="assignTo">派发</param>
        /// <param name="rejectWorkActivity">被退回的活动</param>
        protected virtual void AssignByReject(WorkActivity assignTo, WorkActivity rejectWorkActivity)
        {
            //查找上一步处理人信息
            var previsou = Context.WorkActivities.Include(f => f.DepositUser)
                .Where(f => f.WorkTask.Id == assignTo.WorkTask.Id && f.Status == WorkActivityStatus.Resolve)
                .OrderByDescending(f => f.CreateTime).Take(1).FirstOrDefault();

            if (previsou != null)
            {
                assignTo.AssignTo(previsou.DepositUser);
            }

        }


        public IEnumerable<WorkActivity> GetCurrentWorkActivity(int processInsstanceId, User user)
        {
            var details = Context.WorkActivities.Where(_ =>
                _.ProcessInstance.Id == processInsstanceId && _.Status == WorkActivityStatus.Processing
                                                           && _.DepositUser.Id == user.Id
            ).Select(f => f);
            return details;
        }

        public virtual IQueryable<WorkActivity> GetMyWork(WorkActivitySearcher searcher)
        {
            var roleIds = searcher.Roles.Select(f => f.Id);
            var isDelegateRole = roleIds.Contains(Context.DelegateAdminId()) || roleIds.Contains(Context.AdminRoleId());
            var query = Context.WorkActivities
                .Include(f => f.DepositUser)
                .Include(f => f.ProcessInstance).ThenInclude(f => f.WorkProcess)
                .Include(f => f.WorkTask)
                .Where(workActivity =>
                    (isDelegateRole || workActivity.DepositUser == null && roleIds.Contains(workActivity.Role.Id) ||
                     searcher.UserId == workActivity.DepositUser.Id)
                    && (searcher.Statuses == null || searcher.Statuses.Contains(workActivity.Status))
                    && (searcher.WorkTask == null || workActivity.WorkTask.Name == searcher.WorkTask)
                    && (searcher.ProcessType == null ||
                        searcher.ProcessType.Contains(workActivity.ProcessInstance.WorkProcess.WorkflowType))
                );
            return query;
        }
    }
}